package com.swak.closable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.swak.reactivex.threads.SwakThreadFactory;

/**
 * 参考 motan做的一个全局资源释放工具
 *
 * @author: lifeng
 * @date: 2020/3/29 10:14
 */
public final class ShutDownHook extends Thread {

	private static final Logger logger = LoggerFactory.getLogger(ShutDownHook.class);
	private static final ThreadPoolExecutor shutdownExecutor = new ThreadPoolExecutor(0, 1, 0L, TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>(100),
			new SwakThreadFactory("Close-ExecutorService-Timer", true, new AtomicInteger(1)));
	private static CompletableFuture<Void> shutDownFuture = new CompletableFuture<>();
	private static final int DEFAULT_PRIORITY = 20;

	private static class ShutDownHookHolder {
		private final static ShutDownHook INSTANCE = new ShutDownHook();
	}

	private ArrayList<ClosableObject> resourceList = new ArrayList<>();

	private ShutDownHook() {
	}

	@Override
	public void run() {
		closeAll();
	}

	/**
	 * synchronized method to close all the resources in the list
	 *
	 * @author lifeng
	 * @date 2020/3/29 10:15
	 */
	private synchronized void closeAll() {
		Collections.sort(resourceList);
		for (ClosableObject resource : resourceList) {
			try {
				resource.closable.close();
			} catch (Exception ignored) {
			}
		}
		resourceList.clear();
		shutDownFuture.complete(null);
	}

	/**
	 * 执行关闭
	 *
	 * @param sync 是否同步，如果同步则会在当前线程中执行
	 */
	public static CompletableFuture<Void> runHook(boolean sync) {
		if (sync) {
			ShutDownHookHolder.INSTANCE.closeAll();
		} else {
			ShutDownHookHolder.INSTANCE.start();
		}
		return shutDownFuture;
	}

	/**
	 * 添加关闭任务
	 *
	 * @param closable 添加关闭任务
	 */
	public static void registerShutdownHook(Closable closable) {
		registerShutdownHook(closable, DEFAULT_PRIORITY);
	}

	/**
	 * 添加关闭
	 *
	 * @param closable 添加关闭任务
	 * @param priority 优先级
	 */
	public static synchronized void registerShutdownHook(Closable closable, int priority) {
		ShutDownHookHolder.INSTANCE.resourceList.add(new ClosableObject(closable, priority));
	}

	/**
	 * 可以排序的关闭实体
	 *
	 * @author lifeng
	 */
	private static class ClosableObject implements Comparable<ClosableObject> {
		Closable closable;
		int priority;

		public ClosableObject(Closable closable, int priority) {
			this.closable = closable;
			this.priority = priority;
		}

		@Override
		public int compareTo(ClosableObject o) {
			return Integer.compare(o.priority, this.priority);
		}
	}

	public static boolean isTerminated(Executor executor) {
		if (executor instanceof ExecutorService) {
			if (((ExecutorService) executor).isTerminated()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Use the shutdown pattern from:
	 * https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
	 *
	 * @param executor the Executor to shutdown
	 * @param timeout  the timeout in milliseconds before termination
	 */
	public static void gracefulShutdown(Executor executor, int timeout) {
		if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
			return;
		}
		final ExecutorService es = (ExecutorService) executor;
		try {
			// Disable new tasks from being submitted
			es.shutdown();
		} catch (SecurityException ex2) {
			return;
		} catch (NullPointerException ex2) {
			return;
		}
		try {
			// Wait a while for existing tasks to terminate
			if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
				es.shutdownNow();
			}
		} catch (InterruptedException ex) {
			es.shutdownNow();
			Thread.currentThread().interrupt();
		}
		if (!isTerminated(es)) {
			newThreadToCloseExecutor(es);
		}
	}

	public static void shutdownNow(Executor executor, final int timeout) {
		if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
			return;
		}
		final ExecutorService es = (ExecutorService) executor;
		try {
			es.shutdownNow();
		} catch (SecurityException ex2) {
			return;
		} catch (NullPointerException ex2) {
			return;
		}
		try {
			es.awaitTermination(timeout, TimeUnit.MILLISECONDS);
		} catch (InterruptedException ex) {
			Thread.currentThread().interrupt();
		}
		if (!isTerminated(es)) {
			newThreadToCloseExecutor(es);
		}
	}

	private static void newThreadToCloseExecutor(final ExecutorService es) {
		if (!isTerminated(es)) {
			shutdownExecutor.execute(new Runnable() {
				@Override
				public void run() {
					try {
						for (int i = 0; i < 1000; i++) {
							es.shutdownNow();
							if (es.awaitTermination(10, TimeUnit.MILLISECONDS)) {
								break;
							}
						}
					} catch (InterruptedException ex) {
						Thread.currentThread().interrupt();
					} catch (Throwable e) {
						logger.warn(e.getMessage(), e);
					}
				}
			});
		}
	}

	public static void cancelScheduledFuture(ScheduledFuture<?> scheduledFuture) {
		ScheduledFuture<?> future = scheduledFuture;
		if (future != null && !future.isCancelled()) {
			future.cancel(true);
		}
	}
}
